// Copyright 2013 Petar Maymounkov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ripple

import (
	"bytes"
	"circuit/kit/iomisc"
	"fmt"
	"math/rand"
	"path"
	"strings"
	"time"

	"circuit/kit/rh"
	"circuit/kit/rh/ns"
)

func buildMutexHelp(name string, id uint64) string {
	return fmt.Sprintf(mutexFmt, path.Join("/", name), id)
}

const mutexFmt = `
  This is a mutex ripple, named "%s", with QID ⟨Q%016x⟩.

  SYNOPSIS

  cd cmd # Access command files for this slab.

  cat >> lock          # Open lock file to acquire the mutex lock and hold it.
                       # The open operation will block until the lock can be obtained.
                       # Once opened, the file will consume and discard all writes.
                       # The held lock is released on closing the open file descriptor.
  echo >> lock         # Open lock file to acquire the lock and release the lock right away.
  echo reset >> reset  # Reset the lock and abort all current waiters.
                       # Waiters and the lock holder, if any, will receive
                       # an error on next file operation.
  cat stat             # Read a description of the mutex status.
  cat help             # Read this help screen.

` //

func NewMutex(name string) (*Mutex, rh.FID) {
	m := &Mutex{
		name: name,
		gtm:  time.Now(),
	}

	// lock FID
	m.sem.Init()
	m.lockFID = NewLock(&m.sem)

	// statFID
	m.statFID = ns.RegulateFork(func() ns.UnregulatedFID {
		nhold, nwait := m.sem.Stat()
		body := fmt.Sprintf("NumHolding: %d\nNumWaiting: %d\n", nhold, nwait)
		r := bytes.NewReader([]byte(body))
		//
		return ns.NewReaderFID("mutex/stat", iomisc.ReaderNopCloser(r))
	})

	// helpFID
	m.helpFID = NewHelp(buildMutexHelp(name, uint64(rand.Int63())))

	// resetFID
	m.resetFID = NewEndpoint(func(line string) {
		if strings.TrimSpace(string(line)) != "reset" {
			return
		}
		m.sem.Reset()
	})

	return m, ns.NewFID("mutex", &mutexDir{m})
}

// ▒▒ Mutex
type Mutex struct {
	name string
	gtm  time.Time
	//
	sem      Semaphore
	lockFID  rh.FID
	statFID  rh.FID
	helpFID  rh.FID
	resetFID rh.FID
}

// mutexDir is an ns.InterfaceDir
type mutexDir struct {
	m *Mutex
}

func (md *mutexDir) Lock() rh.FID {
	return md.m.lockFID
}

func (md *mutexDir) Reset() rh.FID {
	return md.m.resetFID
}

func (md *mutexDir) Stat() rh.FID {
	return md.m.statFID
}

func (md *mutexDir) Help() rh.FID {
	return md.m.helpFID
}
